package com.cloudfun.campusshare.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.cloudfun.campusshare.common.constant.AnnouncementType;
import com.cloudfun.campusshare.common.constant.Code;
import com.cloudfun.campusshare.common.exception.BusinessException;
import com.cloudfun.campusshare.common.model.vo.AnnouncementAddVO;
import com.cloudfun.campusshare.common.model.vo.AnnouncementListVO;
import com.cloudfun.campusshare.common.response.BaseResult;
import com.cloudfun.campusshare.common.response.BaseResultFactory;
import com.cloudfun.campusshare.common.response.PageResult;
import com.cloudfun.campusshare.common.security.AuthenticationHolder;
import com.cloudfun.campusshare.entity.AnnouncementEntity;
import com.cloudfun.campusshare.entity.UserEntity;
import com.cloudfun.campusshare.entity.WashPointEntity;
import com.cloudfun.campusshare.repo.AnnouncementEntityRepo;
import com.cloudfun.campusshare.repo.WashPointEntityRepo;
import com.cloudfun.campusshare.service.AnnouncementService;
import com.cloudfun.campusshare.service.CreatorPersistenceService;
import com.cloudfun.campusshare.service.UserRelService;
import com.cloudfun.campusshare.util.BeanCopyUtil;
import com.google.common.collect.Lists;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.util.PropertyPlaceholderHelper;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.Predicate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author : zhangyongjie
 * @version : 1.0
 * @createTime : 2020/6/5 18:26
 * @description :
 */
@Service
public class AnnouncementServiceImpl extends CreatorPersistenceService implements AnnouncementService, InitializingBean {
    @Resource
    private AnnouncementEntityRepo announcementEntityRepo;
    @Resource
    private WashPointEntityRepo washPointEntityRepo;
    @Autowired
    private UserRelService userRelService;

    private DateTimeFormatter df;
    @Resource
    private EntityManager entityManager;

    @Override
    public void afterPropertiesSet() throws Exception {
        super.afterPropertiesSet();
        df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
    }

    @Override
    public void createAnnouncement(AnnouncementAddVO announcementAddVO) {
        LocalDateTime startTime = LocalDateTime.parse(announcementAddVO.getBeginTime(), df);
        LocalDateTime endTime = LocalDateTime.parse(announcementAddVO.getEndTime(), df);
        LocalDateTime publicTime = LocalDateTime.now();

        if (!startTime.isAfter(publicTime) || !endTime.isAfter(startTime)) {
            throw new BusinessException(Code.ERROR, "请填写正确的公告时间");
        }
        if (StringUtils.isBlank(announcementAddVO.getId())) {
            AnnouncementEntity copy = BeanCopyUtil.copy(announcementAddVO, AnnouncementEntity.class, "beginTime", "endTime", "publicTime");

            copy.setBeginTime(startTime);
            copy.setEndTime(endTime);
            copy.setPublicTime(publicTime);

            if (!AuthenticationHolder.isAdmin()) {
                if (AnnouncementType.FOR_INDEX_POINT.equals(announcementAddVO.getType())) {
                    throw new BusinessException(Code.ERROR, "您无法创建该类型公告");
                }
                WashPointEntity washPointEntity = washPointEntityRepo.findById(announcementAddVO.getWashPointId()).orElseThrow(() -> new BusinessException(Code.NODATA));
                copy.setWashPointName(washPointEntity.getName());
            } else {
                if (AnnouncementType.FOR_WASH_POINT.equals(announcementAddVO.getType())) {
                    throw new BusinessException(Code.ERROR, "您无法创建该类型公告");
                }
            }
            //如果已经有完全相同的公告，直接把本条置为下线状态
            if(StringUtils.isBlank(copy.getWashPointId())){
                if (!Objects.isNull(announcementEntityRepo.findByTypeAndTitleAndBeginTimeAndEndTime(copy.getType(), copy.getTitle(), startTime, endTime))) {
                    copy.setIsUsing(false);
                }
            }else{
                if (!Objects.isNull(announcementEntityRepo.findByTypeAndWashPointIdAndTitleAndBeginTimeAndEndTime(copy.getType(), copy.getWashPointId(), copy.getTitle(), startTime, endTime))) {
                    copy.setIsUsing(false);
                }
            }
            announcementEntityRepo.save(copy);
        } else {
            AnnouncementEntity announcementEntity = announcementEntityRepo.findById(announcementAddVO.getId()).orElseThrow(() -> new BusinessException(Code.NODATA));
            announcementEntity.setBeginTime(startTime);
            announcementEntity.setEndTime(endTime);
            announcementEntity.setContent(announcementAddVO.getContent());
            if(startTime.isAfter(publicTime)){
                announcementEntity.setIsUsing(true);
            }
            announcementEntityRepo.save(announcementEntity);
        }
    }

    @Override
    public PageResult queryAnnouncementList(Pageable pageable, String keyword, Set<AnnouncementType> type, String startTime, String endTime) {
        Page<AnnouncementEntity> all = announcementEntityRepo.findAll(assembleCondition(keyword, type, startTime, endTime), pageable);
        return new PageResult(all.getTotalElements(), switchToVO(all.getContent()));
    }

    @Override
    public void offlineAnnouncement(String announcementId) {
        if (AuthenticationHolder.isAdmin()) {
            AnnouncementEntity announcementEntity = announcementEntityRepo.findById(announcementId).orElseThrow(() -> new BusinessException(Code.ERROR));
            announcementEntity.setIsUsing(false);
            announcementEntity.setUpdateTime(String.valueOf(System.currentTimeMillis()));
            announcementEntityRepo.save(announcementEntity);
        } else {
            String currentId = AuthenticationHolder.getUserId();
            UserEntity userMerchant = userRelService.getUserMerchant(currentId);
            AnnouncementEntity announcementEntity = announcementEntityRepo.findByIdAndMerchantId(announcementId, userMerchant.getId());
            if (Objects.isNull(announcementEntity)) {
                throw new BusinessException(Code.NODATA);
            }
            announcementEntity.setIsUsing(false);
            announcementEntity.setUpdateTime(String.valueOf(System.currentTimeMillis()));
            announcementEntityRepo.save(announcementEntity);
        }
    }

    @Override
    public void deleteAnnouncement(String announcementId) {
        if (AuthenticationHolder.isAdmin()) {
            announcementEntityRepo.deleteById(announcementId);
        } else {
            Boolean viewAllData = AuthenticationHolder.getUserAuth().getViewAllData();
            String currentId = AuthenticationHolder.getUserId();
            UserEntity userMerchant = userRelService.getUserMerchant(currentId);
            if (viewAllData) {
                AnnouncementEntity announcementEntity = announcementEntityRepo.findByIdAndMerchantId(announcementId, userMerchant.getId());
                if (Objects.isNull(announcementEntity)) {
                    throw new BusinessException(Code.NODATA);
                }
                announcementEntityRepo.deleteById(announcementId);
            } else {
                Set<String> descendantIds = userRelService.getDescendantIds(currentId);
                descendantIds.add(currentId);
                AnnouncementEntity announcementEntity = announcementEntityRepo.findByIdAndCreatorIdIn(announcementId, descendantIds);
                if (Objects.isNull(announcementEntity)) {
                    throw new BusinessException(Code.NODATA);
                }
                announcementEntityRepo.deleteById(announcementId);
            }
        }
    }

    @Override
    public List<AnnouncementListVO> queryForUserPartList(AnnouncementType type, String washPointId) {
        StringBuilder sb = new StringBuilder("SELECT type,title,content from announcement where is_using=1 and type=:type");
        if (AnnouncementType.FOR_WASH_POINT.equals(type)) {
            sb.append(" and wash_point_id=:washPointId");
        }
        sb.append(" and begin_time <= :localTime and end_time >= :localTime");
        sb.append(" limit 3");

        Query nativeQuery = entityManager.createNativeQuery(sb.toString());
        nativeQuery.setParameter("type", type.name());
        if (AnnouncementType.FOR_WASH_POINT.equals(type)) {
            nativeQuery.setParameter("washPointId", washPointId);
        }
        String now = df.format(LocalDateTime.now());
        nativeQuery.setParameter("localTime", now);
        List<Object[]> resultList = nativeQuery.getResultList();

        return resultList.stream().map(x -> {
            String ty = (String) x[0];
            String title = (String) x[1];
            String content = (String) x[2];
            AnnouncementListVO re = new AnnouncementListVO();
            re.setType(AnnouncementType.valueOf(ty));
            re.setTitle(title);
            re.setContent(content);
            return re;
        }).collect(Collectors.toList());
    }

    @Override
    public BaseResult check(AnnouncementAddVO announcementAddVO) {
        LocalDateTime startTime = LocalDateTime.parse(announcementAddVO.getBeginTime(), df);
        LocalDateTime endTime = LocalDateTime.parse(announcementAddVO.getEndTime(), df);
        List<AnnouncementEntity> list;
        if(StringUtils.isBlank(announcementAddVO.getWashPointId())){
            list = announcementEntityRepo.findByTypeAndBeginTimeGreaterThanEqualAndEndTimeLessThanEqual(
                    announcementAddVO.getType(), startTime, endTime);
        }else{
            // 查询该时间段内是否已经含有相同类型的公告
            list = announcementEntityRepo.findByTypeAndWashPointIdAndBeginTimeGreaterThanEqualAndEndTimeLessThanEqual(
                    announcementAddVO.getType(), announcementAddVO.getWashPointId(), startTime, endTime);
        }
        boolean result = false;
        if(StringUtils.isNotBlank(announcementAddVO.getId()) && CollectionUtils.isNotEmpty(list)){
            list = list.stream().filter(a -> !announcementAddVO.getId().equals(a.getId())).collect(Collectors.toList());
        }
        if(list.size() >= 3){
            result = true;
        }
        if(result){
            StringBuilder builder = new StringBuilder();
            list.stream().limit(3).forEach(a ->{
                builder.append(a.getTitle()).append(",");
            });
            return BaseResultFactory.producrErrorResult(Code.ANNOUNCEMENT_TIME_OVER,new Object[]{builder.substring(0,builder.length() - 1)});
        }
        return BaseResultFactory.produceSuccess();
    }

    private List<AnnouncementListVO> switchToVO(List<AnnouncementEntity> list) {
        return list.stream().map(x -> BeanCopyUtil.copy(x, AnnouncementListVO.class)).collect(Collectors.toList());
    }

    private Specification<AnnouncementEntity> assembleCondition(String keyword, Set<AnnouncementType> type, String startTime, String endTime) {
        return (root, query, criteriaBuilder) -> {
            Boolean viewAllData = AuthenticationHolder.getUserAuth().getViewAllData();
            String userId = AuthenticationHolder.getUserId();
            List<Predicate> predicates = Lists.newArrayList();
            if (!AuthenticationHolder.isAdmin()) {
                if (viewAllData) {
                    UserEntity userMerchant = userRelService.getUserMerchant(userId);
                    predicates.add(criteriaBuilder.equal(root.get("merchantId"), userMerchant.getId()));
                } else {
                    Set<String> descendantIds = userRelService.getDescendantIds(userId);
                    descendantIds.add(userId);
                    predicates.add(root.get("creatorId").in(descendantIds));
                }
            }

            if (StringUtils.isNotBlank(keyword)) {
                predicates.add(criteriaBuilder.like(root.get("title"), keyword));
            }

            if (CollectionUtil.isNotEmpty(type)) {
                predicates.add(root.get("type").in(type));
            }

            if (StringUtils.isNotBlank(startTime) && StringUtils.isNotBlank(endTime)) {
                DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("beginTime"), LocalDateTime.parse(startTime,df)));
                predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("endTime"), LocalDateTime.parse(endTime,df)));
            }
            Predicate[] preArr = new Predicate[predicates.size()];
            return criteriaBuilder.and(predicates.toArray(preArr));
        };
    }

    @Override
    protected Class<?> getEntityClass() {
        return AnnouncementEntity.class;
    }
}
